package com.thinkit.bigdata.core.util;


import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;

import java.io.*;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

/**
 * 压缩文件的支持工具
 *
 * @author shaohailong
 */
public class JCZipTools {

    static final int BUFFER = 2048;
    private static final Logger log = LogManager.getLogger(JCZipTools.class);

    public JCZipTools() {
    }

    /**
     * 压缩指定目录
     *
     * @param compressedFilePath 需要被压缩的目录
     * @param zipFileRootPath    压缩到的zip文件的根路径
     * @param zipFileName        压缩到的zip文件的文件名
     * @return boolean 成功true; 失败false;
     * @throws IOException
     */
    public static boolean compressFolder2Zip(String compressedFilePath, String zipFileRootPath, String zipFileName)
            throws IOException {
        File compressedFile = new File(compressedFilePath);

        if ("".equalsIgnoreCase(zipFileName)) {
            zipFileName = (compressedFilePath);
        }

        if (!(zipFileRootPath).endsWith(File.separator)) {
            zipFileRootPath = zipFileRootPath + File.separator;
        }

        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileRootPath + zipFileName));

        out.setEncoding("GBK");

        log.debug("compress [" + compressedFilePath + "] start!");

        boolean result;

        result = compressFolder2Zip(compressedFile, out, "")
                && updateCompressFolderDate(compressedFile, out, "");

        log.debug("compress [" + compressedFilePath + "] end!");

        out.close();

        return result;

    }

    /**
     * 压缩文件夹,内部使用
     *
     * @param compressedFile
     * @param out
     * @param base
     * @return
     */
    private static boolean compressFolder2Zip(File compressedFile, ZipOutputStream out, String base) {
        FileInputStream fileInputStream = null;

        try {

            if ("".equals(base) || base == null) {
                base = compressedFile.getName();
            }

            if (compressedFile.isDirectory()) {
                File[] childrenCompressedFileList = compressedFile.listFiles();
                if (!base.endsWith(File.separator) && base != null && !"".equals(base)) {
                    base = base + File.separator;
                }

                for (int i = 0; i < childrenCompressedFileList.length; i++) {
                    compressFolder2Zip(childrenCompressedFileList[i],
                            out, base + childrenCompressedFileList[i].getName());
                }

            } else {

                log.debug("compressing :" + compressedFile.getPath());
                ZipEntry ze = new ZipEntry(base);
                ze.setTime(compressedFile.lastModified());

                out.putNextEntry(ze);
                fileInputStream = new FileInputStream(compressedFile);
                int len;
                byte[] buf = new byte[BUFFER];
                while ((len = fileInputStream.read(buf)) != -1) {
                    out.write(buf, 0, len);
                }

                fileInputStream.close();
                out.flush();
//                out.close();
            }

            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 更新被压缩目录的最后修改时间,内部使用
     *
     * @param compressedFile
     * @param out
     * @param base
     * @return
     */
    private static boolean updateCompressFolderDate(File compressedFile, ZipOutputStream out, String base) {
        try {
            File[] childrenCompressedFileList = compressedFile.listFiles();


            if (compressedFile.isDirectory()) {

                if ("".equals(base) || base == null) {
                    base = compressedFile.getName();
                }

                if (!base.endsWith(File.separator)) {
                    base = base + File.separator;
                }

                ZipEntry ze = new ZipEntry(base);

                ze.setTime(compressedFile.lastModified());

                out.putNextEntry(ze);

                out.flush();


                for (int i = 0; i < childrenCompressedFileList.length; i++) {
                    updateCompressFolderDate(childrenCompressedFileList[i], out, base + childrenCompressedFileList[i].getName());
                }

            }
            return true;
        } catch (IOException e) {
            log.error("update compress folder date error!", e);
            return false;
        }
    }

    /**
     * 将多个目录压缩
     *
     * @param compressedFilePaths
     * @param zipFileRootPath
     * @param zipFileName
     * @return
     */
    public static boolean compressFolders2Zip(String[] compressedFilePaths, String zipFileRootPath, String zipFileName) {
        ZipOutputStream out = null;
        boolean result;
        try {

            result = true;
            if (!(zipFileRootPath).endsWith(File.separator)) {

                zipFileRootPath = zipFileRootPath + File.separator;

            }
            out = new ZipOutputStream(new FileOutputStream(zipFileRootPath + zipFileName));
            out.setEncoding("GBK");
            log.debug("compress start!");
            for (String compressedFilePath : compressedFilePaths) {

                File compressedFile = new File(compressedFilePath);

                result = compressFolder2Zip(compressedFile, out, "") && updateCompressFolderDate(compressedFile, out, "");

                if (!result) {
                    return result;
                }
            }
            log.debug("compress end!");
            return result;
        } catch (FileNotFoundException ex) {
            log.error(ex.getMessage(), ex);
            System.exit(0);
            return false;
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                System.exit(0);
                log.error(ex.getMessage(), ex);
            }
        }
    }

    /**
     * 压缩给定的文件或者文件夹, 压缩包内的路径, 会是给定的路径的相对部分
     *
     * @param filePaths
     * @param relativedGivenPath
     * @param zipFileRootPath
     * @param zipFileName
     * @return
     */
    public static boolean compressFilesRelative2GivenPath(String[] filePaths, String relativedGivenPath, String zipFileRootPath, String zipFileName) {
        boolean result = true;
        ZipOutputStream out = null;
        try {


            if (!(zipFileRootPath).endsWith(File.separator)) {

                zipFileRootPath = zipFileRootPath + File.separator;

            }

            new File(zipFileRootPath).mkdirs();

            out = new ZipOutputStream(new FileOutputStream(zipFileRootPath + zipFileName));

            out.setEncoding("GBK");

            log.debug("compress start!");

            if (!relativedGivenPath.endsWith(File.separator)) {
                relativedGivenPath += File.separator;
            }

            if (filePaths.length == 0) {
            } else {
                for (String filePath : filePaths) {

                    if ("".equals(filePath) || filePath == null) {
                    } else {
                        //设置压缩文件中被压缩文件相对于压缩包根目录的路径
                        String zipFileBase = filePath.replace(relativedGivenPath, "");
                        //实例化被压缩文件
                        File compressedFile = new File(filePath);
                        //将别压缩文件写入压缩包,并更新文件的日期
                        result = compressFolder2Zip(compressedFile, out, zipFileBase) && updateCompressFolderDate(compressedFile, out, zipFileBase);
                        //如果上一步过程出现任何异常或者失败的情况..返回false
                        if (!result) {

                            return result;

                        }
                    }

                }
            }

            return result;

        } catch (Exception ex) {

            log.error(ex.getMessage(), ex);

            return false;
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                log.error(ex.getMessage(), ex);
            }
        }
    }

    /**
     * 将给定的文件列表(支持文件夹), 压缩到特定的zip文件
     *
     * @param filePaths
     * @param relativedGivenPath
     * @param zipFileRootPath
     * @param zipFileName
     * @return
     */
    public static boolean compressFilesRelative2GivenPath(List<String> filePaths, String relativedGivenPath, String zipFileRootPath, String zipFileName) {
        boolean result = true;
        ZipOutputStream out = null;
        try {
            if (!(zipFileRootPath).endsWith(File.separator)) {
                zipFileRootPath = zipFileRootPath + File.separator;
            }

            new File(zipFileRootPath).mkdirs();

            out = new ZipOutputStream(new FileOutputStream(zipFileRootPath + zipFileName));

            out.setEncoding("GBK");

            log.debug("compress start!");

            if (!relativedGivenPath.endsWith(File.separator)) {
                relativedGivenPath += File.separator;
            }

            if (filePaths.isEmpty()) {
            } else {
                for (String filePath : filePaths) {

                    if ("".equals(filePath) || filePath == null) {
                    } else {
                        //设置压缩文件中被压缩文件相对于压缩包根目录的路径
                        String zipFileBase = filePath.replace(relativedGivenPath, "");
                        //实例化被压缩文件
                        File compressedFile = new File(filePath);
                        //将别压缩文件写入压缩包,并更新文件的日期
                        result = compressFolder2Zip(compressedFile, out, zipFileBase) && updateCompressFolderDate(compressedFile, out, zipFileBase);
                        //如果上一步过程出现任何异常或者失败的情况..返回false
                        if (!result) {

                            return result;

                        }
                    }

                }
            }


            return result;

        } catch (Exception ex) {

            log.error(ex.getMessage(), ex);

            return false;
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                log.error(ex.getMessage(), ex);
            }
        }
    }

    /**
     * 解压缩文件
     *
     * @param zipFilePath 被解压的zip文件路径
     * @param releasePath 解压到的路径
     * @throws IOException
     */
    public static void decompressFile(String zipFilePath, String releasePath) throws IOException {
        ZipFile zipFile = new ZipFile(zipFilePath, "GBK");
        Enumeration<ZipEntry> enumeration = zipFile.getEntries();
        InputStream inputStream = null;
        FileOutputStream fileOutputStream = null;
        ZipEntry zipEntry = null;
        String zipEntryNameStr = "";

        while (enumeration.hasMoreElements()) {
            zipEntry = (ZipEntry) enumeration.nextElement();
            zipEntryNameStr = zipEntry.getName();

            String path = releasePath;
            File file2UnZip = new File(path, zipEntryNameStr);
            if (!file2UnZip.getParentFile().exists()) {
                file2UnZip.getParentFile().mkdirs();
            }

            if (zipEntry.isDirectory()) {
                file2UnZip.mkdir();
            } else {

                if (file2UnZip.exists()) {
                    file2UnZip.delete();
                }
                inputStream = zipFile.getInputStream(zipEntry);

                fileOutputStream = new FileOutputStream(file2UnZip);

                log.debug("Extracting:" + file2UnZip.getPath());
                byte[] buf = new byte[BUFFER];
                int len;
                while ((len = inputStream.read(buf)) > 0) {
                    fileOutputStream.write(buf, 0, len);
                }
                fileOutputStream.flush();

                inputStream.close();
                fileOutputStream.close();
            }

            file2UnZip.setLastModified(zipEntry.getTime());

        }

        zipFile.close();

        log.info("decompress successfully!!-----:)");
    }

    /**
     * 将zip压缩包内给定类型的文件,给定前缀的文件,统一解压到给定目录
     *
     * @param zipToPath  解压到的目录
     * @param unZipFile  被解压的文件
     * @param fileType   文件类型
     * @param filePrefix 文件前缀
     * @throws Exception
     */
    public static void decompressFile2SameFolder(File zipToPath, File unZipFile, String fileType, String filePrefix) throws Exception {


        if (!unZipFile.exists()) {
            return;
        }

        if (!zipToPath.exists()) {
            zipToPath.mkdirs();
        }

        if (!zipToPath.isDirectory()) {
            return;
        }


        int i = 0;

        try {
            BufferedOutputStream dest = null;
            BufferedInputStream is = null;
            ZipEntry entry;
            ZipFile zipfile = new ZipFile(unZipFile, "GBK");
            Enumeration e = zipfile.getEntries();
            while (e.hasMoreElements()) {
                entry = (ZipEntry) e.nextElement();

                if (entry.isDirectory()) {
                    continue;
                } else if (filePrefix != null && entry.getName().startsWith(filePrefix) && entry.getName().endsWith(fileType)) {

                    File tmp = new File(zipToPath, entry.getName());
                    File tmp2 = new File(zipToPath, String.valueOf(new Date().getTime()) + "-" + i + "-" + tmp.getName());

                    log.info("Extracting: " + entry);
                    is = new BufferedInputStream(zipfile.getInputStream(entry));
                    int count;
                    byte data[] = new byte[BUFFER];
                    FileOutputStream fos = new FileOutputStream(tmp2);
                    dest = new BufferedOutputStream(fos, BUFFER);
                    while ((count = is.read(data, 0, BUFFER))
                            != -1) {
                        dest.write(data, 0, count);
                    }
                    dest.flush();
                    dest.close();
                    is.close();
                    i++;
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
//        String[] filePaths = new String[]{
//                "E:/book/novel/嫁嫡.txt",
//                "E:/book/novel/四神集团①：首席总裁的逃妻.txt",
//                "E:/book/novel/极品男子公寓.txt"};
//        String zipFileName = new Date().getTime()+".zip";
//        log.info(compressFilesRelative2GivenPath(filePaths, "E:\\", "E:\\", zipFileName));
        String compressedFilePath = "E:/book/novel";
        String zipFileRootPath = "E:/";
        String zipFileName = new Date().getTime() + ".zip";
        try {
            boolean res = compressFolder2Zip(compressedFilePath, zipFileRootPath, zipFileName);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
